package com.ruoyi.system;

import org.objectweb.asm.Type;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;

import static java.lang.reflect.Modifier.*;

public class M {

    private static final Set<Integer> invokeOps = new HashSet<Integer>() {{
        add(Opcodes.INVOKEVIRTUAL);
        add(Opcodes.INVOKESPECIAL);
        add(Opcodes.INVOKESTATIC);
        add(Opcodes.INVOKEINTERFACE);
    }};

    public static void main(String[] args) throws ClassNotFoundException, IOException, NoSuchMethodException {

        Map<String, byte[]> stringMap = loadByteCode();
        Map<String, Class<?>> classMap = loadClass();

        byte[] bytes = stringMap.get("SysConfigServiceImpl.class");

        ClassReader cr = new ClassReader(bytes);
        ClassNode cn = new ClassNode();
        cr.accept(cn, 0);

        StringBuilder sb = new StringBuilder();

        Set<String> set = new HashSet<>();

        // 遍历所有方法
        for (MethodNode mn : cn.methods) {

            if (mn.name.startsWith("$")) {
                System.out.println(mn.name + " start with $");
                continue;
            }

            AbstractInsnNode[] instructions = mn.instructions.toArray();
            if (instructions.length == 0) {
                System.out.println(mn.name + " is native");
                continue;
            }

            // 当前方法的 所有字节码指令
            for (int i = 0; i < instructions.length; i++) {

                // 当前指令
                AbstractInsnNode instruction = instructions[i];

                if (instruction instanceof MethodInsnNode) {
                    MethodInsnNode methodInsnNode = (MethodInsnNode) instruction;
                    // 当前指令 是 方法调用指令
                    if (invokeOps.contains(instruction.getOpcode())) {

                        String className = methodInsnNode.owner;
                        String methodName = methodInsnNode.name;
                        String desc = methodInsnNode.desc;

                        if (set.contains(className + methodName + desc)) {
                            continue;
                        } else {
                            set.add(className + methodName + desc);
                        }

                        if (!methodName.equals("<init>")) {

                            System.out.println(mn.name
                                    + " 调用了 ------- 类名: " + className
                                    + " ---- 方法名: " + methodName
                                    + " ---- 方法描述符: " + desc);

                            Class type = classMap.get(className.replace("/", "."));
                            if (type == null) {
                                type = Class.forName(className.replace("/", "."));
                            }

                            // 当前方法的入参类型
                            Type[] argumentTypes = Type.getArgumentTypes(desc);

                            // 当前方法调用的 目标方法
                            Method method = getMethod(type, methodName, argumentTypes);
                            if (method == null) {
                                System.err.println(className + "\t" + methodName + "\t" + "\t" + desc);
                            } else {
                                String s = genMethodText(type, method);
                                sb.append(s);
                            }

                        }
                    }
                }

            }
        }

        System.out.println("==============");
        System.out.println(sb.toString());
    }

    public static String genMethodText(Class type, Method method) {
        String typeName = type.getName();
        String methodName = method.getName();

        final String start = "    ";

        StringBuilder sb = new StringBuilder()
                .append(start)
                .append("@MockInvoke(targetClass = " + typeName + ".class, targetMethod = \"" + methodName + "\")\r\n");

        int mod = method.getModifiers();

        sb.append(start);
        if (isPrivate(mod)) {
            sb.append("private ");
        } else if (isProtected(mod)) {
            sb.append("protected ");
        } else if (isPublic(mod)) {
            sb.append("public ");
        }

        if (isStatic(mod)) {
            sb.append("static ");
        }

        // todo 类型参数
        Class<?> returnType = method.getReturnType();
        String retType = returnType.toString().replace("class", "").replace("interface", "");
        sb.append(retType + " ");

        // todo 类型参数
        Class<?>[] parameterTypes = method.getParameterTypes();
        StringBuilder sbp = new StringBuilder();
        for (int i = 0; i < parameterTypes.length; i++) {
            sbp.append(parameterTypes[i].toString().replace("class", "").replace("interface", ""))
                    .append(" var" + i);
            if (i != parameterTypes.length - 1) {
                sbp.append(",");
            }
        }

        sb.append(methodName + "(" + sbp + ") { \r\n");

        if (!"void".equals(retType)) {

            if (returnType.isPrimitive()) {
                sb.append(start);
                sb.append(start);
                if (returnType == boolean.class) {
                    sb.append("return true;\r\n");
                } else if (returnType == byte.class) {
                    sb.append("return (byte) 0;\r\n");
                } else if (returnType == char.class) {
                    sb.append("return 'c';\r\n");
                } else if (returnType == short.class) {
                    sb.append("return (short) 4;\r\n");
                } else if (returnType == int.class) {
                    sb.append("return 213;\r\n");
                } else if (returnType == float.class) {
                    sb.append("return (float) 1.1;\r\n");
                } else if (returnType == long.class) {
                    sb.append("return 123L;\r\n");
                } else if (returnType == double.class) {
                    sb.append("return 123.455;\r\n");
                }

            } else {
                sb.append(start);
                sb.append(start);
                sb.append("return null;\r\n");
            }
        }

        sb.append(start);
        sb.append("}\r\n");

        String x = sb.toString();
        System.out.println(x);
        return x;
    }


    public static Method getMethod(Class<?> type,
                                   String methodName,
                                   Type[] argumentTypes)
            throws ClassNotFoundException, NoSuchMethodException {

        Class[] types = new Class[argumentTypes.length];

        for (int i = 0; i < argumentTypes.length; i++) {
            Type argumentType = argumentTypes[i];
            String s = argumentType.toString();
            Class c = null;
            if ("B".equals(s)) {
                c = byte.class;
            } else if ("C".equals(s)) {
                c = char.class;
            } else if ("D".equals(s)) {
                c = double.class;
            } else if ("F".equals(s)) {
                c = float.class;
            } else if ("I".equals(s)) {
                c = int.class;
            } else if ("J".equals(s)) {
                c = long.class;
            } else if ("S".equals(s)) {
                c = short.class;
            } else if ("Z".equals(s)) {
                c = boolean.class;
            } else if ("V".equals(s)) {
                c = void.class;
            } else if (s.startsWith("L")) {
                int l = s.length();
                s = s.substring(1, l - 1).replace("/", ".");
                c = Class.forName(s);
            } else if (s.startsWith("[")) {
                int l = s.length();
                s = s.substring(2, l - 1).replace("/", ".");
                c = Array.newInstance(Class.forName(s), 0).getClass();
            } else {
                System.err.println("未知类型" + s);
                c = null;
            }
            types[i] = c;
        }

        try {
            Method method = type.getDeclaredMethod(methodName, types);
            return method;
        } catch (NoSuchMethodException e) {

            Class<?> superclass = type.getSuperclass();
            return getMethod(superclass, methodName, argumentTypes);

        } catch (Exception e) {
            System.err.println(e.getMessage());
            return null;
        }
    }


    /**
     * @return key -> com.xxx.xxx val -> Class<?>
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Map<String, Class<?>> loadClass() throws IOException, ClassNotFoundException {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = contextClassLoader.getResources("../classes/com");
        URL url = resources.nextElement();
        String filePath = url.getFile();
        List<File> fileList = new LinkedList<>();

        loadByteCode(new File(filePath), fileList);
        Map<String, Class<?>> classMap = new HashMap<>(fileList.size());

        for (File file : fileList) {
            String s = "\\";
            if (System.getProperties().getProperty("os.name").contains("Windows")) {
                s = "\\";
            } else {
                s = "/";
            }

            try {
                String classes = file.getAbsolutePath().split("classes.")[1].replace(s, ".").replace(".class", "");
//                System.out.println("load class:" + classes);
                classMap.put(classes, Class.forName(classes));
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        return classMap;
    }

    /**
     * @return key -> XXX.class val -> 字节码
     * @throws IOException
     */
    public static Map<String, byte[]> loadByteCode() throws IOException {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = contextClassLoader.getResources("../classes/com");
        URL url = resources.nextElement();
        String filePath = url.getFile();
        List<File> fileList = new LinkedList<>();

        loadByteCode(new File(filePath), fileList);

        Map<String, byte[]> bytecode = new HashMap<>();

        byte[] buf = new byte[4096];

        for (File file : fileList) {
            try (FileInputStream fis = new FileInputStream(file);
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();) {

                int i = -1;
                while ((i = fis.read(buf)) > 0) {
                    baos.write(buf, 0, i);
                }

                byte[] bytes = baos.toByteArray();
                bytecode.put(file.getName(), bytes);
            }
        }

        return bytecode;
    }

    /**
     * 加载当前工程下的所有文件
     *
     * @param file
     * @param fileList 出参
     */
    public static void loadByteCode(File file, List<File> fileList) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                if (f.isDirectory()) {
                    loadByteCode(f, fileList);
                } else {
                    fileList.add(f);
                }
            }
        } else {
            fileList.add(file);
        }
    }

}
